Compiler.prototype.write = function(writer,tree) {
	try {
		for (var i = 0; i < tree.length; i++) {
			var node = tree[i];
			switch (node.type) {
				case Compiler.TOKEN_CLASS: this.writeClass(writer,node,null,''); break;
				default: throw new CompilerError('Build error: unknow top level node');
			}
		}
	} catch (ex) {
		System.log(ex.message + '\n\n');
	}
}

Compiler.prototype.tab = '\t';
Compiler.prototype.prefix = '_';

Compiler.prototype.writeClass = function(writer,node,context,t) {
	writer.write(t);
	// write constructor
	if (context) writer.write('_' + context.name + '.prototype.' + this.prefix);
	else writer.write('var ');
	writer.writeln(this.prefix + node.name + ' = function() {');
	// init member variables inside the constructor
	var costructor = null;
	for (var i = 0; i < node.members.length; i++) {
		var cn = node.members[i];
		if (cn.type == Compiler.TOKEN_VAR && !cn.isStatic) {
			for (var j = 0; j < cn.variables.length; j++) this.writeVariable(writer,
				t + this.tab + 'this.',cn.variables[j],node);
		} else if (cn.type == Compiler.TOKEN_FUNCTION && cn.name == node.name) {
			constructor = cn;
		}
	}
	if (constructor && constructor.block)
		this.writeStatements(writer,constructor.block.statements,node,t + this.tab);
	writer.writeln('}');
	// write static variables
	var first = true;
	for (i = 0; i < node.members.length; i++) {
		cn = node.members[i];
		if (cn.type == Compiler.TOKEN_VAR && cn.isStatic) {
			if (first) {
				writer.writeln();
				first = false;
			}
			for (var j = 0; j < cn.variables.length; j++) this.writeVariable(writer,
				t + this.prefix + node.name + '.prototype.',cn.variables[j],node);
			
		}
	}
	writer.writeln();
	// write functions
	for (i = 0; i < node.members.length; i++) {
		cn = node.members[i];
		if (cn.type != Compiler.TOKEN_VAR && cn != constructor) 
			this.writeFunction(writer,cn,node,t);
	}
	
};

Compiler.prototype.writeFunction = function(writer,node,context,t) {
	writer.write(t);
	if (context) writer.write(this.prefix + context.name + '.prototype.');
	writer.writeln(this.prefix + node.name + ' = function() {');
	this.writeStatements(writer,node.block.statements,context,t + this.tab);
	writer.writeln(t + '}');
	writer.writeln();
};

Compiler.prototype.writeVariable = function(writer,prefix,node,context) {
	writer.write(prefix + this.prefix + node.name);
	if (node.initValue) {
		writer.write(' = '); 
		this.writeExpression(writer,node.initValue,context);
	} else {
		writer.write(' = null');
	}
	writer.writeln(';');
};

Compiler.prototype.writeStatements = function(writer,statements,context,t) {
	for (var i = 0; i < statements.length; i++) {
		this.writeStatement(writer,statements[i],context,t);
	}
}

Compiler.prototype.writeStatement = function(writer,node,context,t,hideTab) {
	if (node.type == Compiler.TOKEN_VAR) {
		for (var i = 0; i < node.variables.length; i++)
			this.writeVariable(writer,hideTab ? '' : t + 'var ',node.variables[i]);
		return;
	}
	writer.write(hideTab ? '' : t);
	switch (node.type) {
		case Compiler.TOKEN_LABEL:
			writer.writeln(node.label + ':');
			break;
		case Compiler.TOKEN_BLO:
			writer.writeln('{');
			this.writeStatements(writer,node.statements,context,t + this.tab);
			writer.writeln(t + '}');
			break;
		case Compiler.TOKEN_IF:
			writer.write('if (');
			this.writeExpression(writer,node.condition,context);
			writer.write(') ');
			this.writeStatement(writer,node.statIf,context,t,true);
			if (node.statElse) {
				writer.write(t + 'else '); 
				this.writeStatement(writer,node.statElse,context,t,true);
			}
			break;
		case Compiler.TOKEN_FOR:
			writer.write('for (');
			this.writeExpression(writer,node.expr1,context);
			writer.write(';');
			this.writeExpression(writer,node.expr2,context);
			writer.write(';');
			this.writeExpression(writer,node.expr3,context);
			writer.write(') ');
			this.writeStatement(writer,node.statement,context,t,true);
			break;
		case Compiler.TOKEN_DO:
			writer.write('do ');
			this.writeStatement(writer,node.statement,context,t,true);
			writer.write('while (');
			this.writeExpression(writer,node.condition,context);
			writer.writeln(');');
		case Compiler.TOKEN_WHILE:
			writer.write('while (');
			this.writeExpression(writer,node.condition,context);
			writer.write(') ');
			this.writeStatement(writer,node.statement,context,t,true);
			break;
		case Compiler.TOKEN_SWITCH:
			writer.write('switch (');
			this.writeExpression(writer,node.expr,context);
			writer.writeln(') {');
			for (var i = 0; i < node.blocks.length; i++) {
				var sn = node.blocks[i];
				if (sn.type == Compiler.TOKEN_CASE) {
					writer.write(t + this.tab + 'case ');
					this.writeExpression(writer,sn.expr,context);
				} else { // deafault
					writer.write(t + this.tab + 'default');
				}
				writer.writeln(':');
				this.writeStatements(writer,sn.statements,context,t + this.tab + this.tab,true);
			}
			writer.writeln(t + '}');
			break;
		case Compiler.TOKEN_BREAK:
			writer.write('break');
			if (node.label) writer.write(' ' + node.label);
			writer.writeln(';');
			break;
		case Compiler.TOKEN_CONTINUE:
			writer.write('continue');
			if (node.label) writer.write(' ' + node.label);
			writer.writeln(';');
			break;
		case Compiler.TOKEN_RETURN:
			writer.write('return');
			if (node.expr) {
				writer.write(' ');
				this.writeExpression(writer,node.expr,context);
			}
			writer.writeln(';');
			break;
		case Compiler.TOKEN_TRY:
			writer.write('try ');
			this.writeBlockStatement(writer,node.statement,context,t);
			if (node.catchBlocks.length > 0) {
				writer.writeln(t + 'catch (ex) {');
				for (var i = 0; i < node.catchBlocks.length; i++) {
					var sn = node.catchBlocks[i]
					if (i == 0 && !sn.dataType) {
						if (sn.statement) {
							if (sn.statement.type == Compiler.TOKEN_BLO) 
								this.writeStatements(writer,sn.statement.statements,
								context,t + this.tab);
							else this.writeStatement(writer,sn.statement,context,t + this.tab);
						} else writer.writeln(t + this.tab + '// NOOP');						
						break;
					} else if (!sn.dataType) {
						writer.write(t + this.tab + 'else');
						this.writeBlockStatement(writer,sn.statement,context,t);
						break;
					} else {
						writer.write(t + this.tab + (i == 0 ? '' : 'else ') + 
							'if (instance(ex,' + this.prefix + sn.dataType + ')) ');
						if (sn.statement) this.writeStatement(writer,sn.statement,
							context,t + this.tab,true);
						else writer.writeln(';');
					}
				}
				if (node.finallyStat) {
					writer.write(t + '} finally ');
					this.writeBlockStatement(writer,sn.finallyStat,context,t);
				} else writer.writeln('}');
			}
			break;
		default:
			this.writeExpression(writer,node,context);
			writer.writeln(';'); 
	}
}

Compiler.prototype.writeBlockStatement = function(writer,node,context,t) {
	if (node) {
		if (node.type == Compiler.TOKEN_BLO) {
			this.writeStatement(writer,node,context,t,true);
		} else {
			writer.writeln('{');
			this.writeStatement(writer,node,context,t + this.tab,true);
			writer.writeln(t + '}');
		}
	} else {
		writer.writeln('{');
		writer.writeln(t + this.tab + '// NOOP');
		writer.writeln(t + '}');
	}
}

Compiler.prototype.operators = {};

for (key in Compiler.OPERATORS) {
	if (Compiler.OPERATORS[key] instanceof Object) {
		for (var key2 in Compiler.OPERATORS[key]) {
			Compiler.prototype.operators[Compiler.OPERATORS[key][key2]] = key2; 
		}
	} else {
		Compiler.prototype.operators[Compiler.OPERATORS[key]] = key; 
	}
}

Compiler.prototype.writeExpression = function(writer,expr,context,parenPrec) {
	switch (expr.type) {
		case Compiler.TOKEN_MAP:
			var sep = '{';
			for (var i = 0; i < expr.values.length; i++) {
				var sn = expr.values[i];
				writer.write(sep);
				this.writeExpression(writer,sn.key);
				writer.write(':');
				this.writeExpression(writer,sn.value);
				sep = ',';
			}
			writer.write('}');
			break;
		case Compiler.TOKEN_ARRAY:
			var sep = '[';
			for (var i = 0; i < expr.values.length; i++) {
				writer.write(sep);
				this.writeExpression(writer,expr.values[i]);
				sep = ',';
			}
			writer.write(']');
			break;
		case Compiler.TOKEN_NUMBER:
			writer.write(expr.value);
			break;			
		case Compiler.TOKEN_STRING:
			writer.write('"' + expr.value + '"');
			break;
		case Compiler.TOKEN_ID:
			writer.write(this.prefix + expr.name);
			break;
		default:
			var prec = Compiler.PRECEDENCE[expr.type];
			var paren =  parenPrec < prec;
			if (paren) writer.write('(');
			this.writeExpression(writer,expr.left,context,prec);
			writer.write(this.operators[expr.type]);
			this.writeExpression(writer,expr.right,context,prec);
			if (paren) writer.write(')');
	}
}
